package org.yun.lottery;

import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.common.utils.UuidUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.yun.biz.dao.RecordRepository;
import org.yun.biz.dto.PrizeDTO;
import org.yun.biz.model.Lottery;
import org.yun.biz.model.Prize;
import org.yun.biz.model.Record;
import org.yun.config.IConfig;
import org.yun.constants.Constant;
import org.yun.exception.BizException;
import org.yun.exception.ErrorCode;
import org.yun.util.*;
import org.yun.worker.ReplenishStock;

import javax.annotation.Resource;
import java.util.*;

import static org.yun.constants.Constant.NO_STOCK;
import static org.yun.constants.RedisConstant.*;
import static org.yun.util.RedisUtil.*;

/**
 * @ProjectName: no-concurrent
 * @ClassName: LotteryImpl
 * @Description: nginx限流+guava限流+（黑白名单）+（随机舍弃+token）+bitmap是否参加过+redis扣除库存+同步redis
 * @Author: liyunfeng31
 * @Date: 2020/10/4 23:22
 */
@Slf4j
@Service
public class LotteryServiceImpl implements LotteryService {


    @Resource
    private IConfig config;

    @Resource
    private RedisUtil redisUtil;

    @Resource
    private ReplenishStock replenishStock;

    @Resource
    private RabbitTemplate rabbitTemplate;

    @Resource
    private RecordRepository recordDao;


    @Override
    public Boolean applyChance(Long userId, Long lotteryId, int num, long expireTime){
        String key = CHANCE_UID + lotteryId + ":"+userId;
        return redisUtil.set(key,num,expireTime);
    }

    @Override
    public PrizeDTO luckLottery(Long userId, Long lotteryId) {
        // 从配置中心OrCache获取活动信息并校验
        Lottery lottery = getLottery(lotteryId);

        String chanceKey = CHANCE_UID + lotteryId+":"+userId;
        Long chanceCheck = redisUtil.executeScript(DECR_SCRIPT, Collections.singletonList(chanceKey));
        if(!Constant.SUCCESS.equals(chanceCheck)){
            throw new BizException(ErrorCode.LOTTERY.NO_TICKET);
        }

        Map<String, Prize> result = ArithmeticUtil.doLottery(lottery.getPrizes());
        Prize prize = result.get(Constant.LUCK);
        Prize thanks = result.get(Constant.THANKS);

        // 谢谢参与
        String topic = lottery.getTopic();
        int prizeType = prize.getPrizeType();
        if(prizeType == Constant.PRIZE_THANKS){
            return buildPrizeDTO(userId, thanks, topic);
        }

        // 中奖扣除库存
        Long prizeId = prize.getPrizeId();
        List<Long> noStocks = config.getNoStockSkuList(Long.class);
        if(!CollectionUtils.isEmpty(noStocks) && noStocks.contains(prizeId)){
            return buildPrizeDTO(userId, thanks, topic);
        }

        // redis 减库存
        String stockKey = PRIZE_ID + prizeId;
        if(prizeType == Constant.PRIZE_NORMAL){
            Long decrResult = redisUtil.executeScript(DECR_SCRIPT, Collections.singletonList(stockKey));
            if(Constant.SUCCESS.equals(decrResult)){
                return buildPrizeDTO(userId, prize, topic);
            }
            noStocks.add(prizeId);
            config.publishConfig(NO_STOCK_SKU_LIST + JSON.toJSONString(noStocks));
            return buildPrizeDTO(userId, thanks, topic);
        }

        // redis 减库存 且消耗coupon
        PrizeDTO first = tryGetPrize(stockKey, topic, prize, userId);

        if(first != null){ return first; }
        // 有库存 缓存没券了 补货
        String uid = IDUtil.id().toString();
        try {
            boolean lock = redisUtil.tryLock(REPLENISH_LOCK + prizeId, uid, 5, 2);
            if(lock){
                PrizeDTO second = tryGetPrize(stockKey, topic, prize, userId);
                if(second != null){ return second; }
                // 同步补货
                int num = replenishStock.doReplenishStock(prizeId, Constant.FULL_STOCK);
                if(num == 0){ return buildPrizeDTO(userId, thanks, topic); }

                PrizeDTO third = tryGetPrize(stockKey, topic, prize, userId);
                if(third != null){ return third; }
            }
        }catch(Exception e){
            log.error("prizeId: {} pop coupon error",prizeId, e);
        }finally{
            redisUtil.unLock(REPLENISH_LOCK + prizeId, uid);
        }
        return buildPrizeDTO(userId, thanks, topic);
    }


    /**
     * 尝试获取奖品券码
     * @param stockKey 库存key
     * @param topic 活动主题
     * @param prize 奖品
     * @param userId 用户ID
     * @return DTO
     */
    private PrizeDTO tryGetPrize(String stockKey, String topic, Prize prize, Long userId){
        Long prizeId = prize.getPrizeId();
        String couponKey = COUPON_LIST + prizeId;
        String codePwd = redisUtil.executeComplexScript(COUPON_SCRIPT, Arrays.asList(stockKey,couponKey,COUPON_REPLENISH_LIST), "500",prizeId.toString());
        if(StringUtils.isEmpty(codePwd) || NO_STOCK.equals(codePwd)){ return null; }
        String[] arr = codePwd.split(Constant.JOIN);
        prize.setCode(AesUtil.decrypt(arr[0]));
        prize.setPwd(AesUtil.decrypt(arr[1]));
        return buildPrizeDTO(userId, prize, topic);
    }


    /**
     * 返回奖品对象
     * @param userId 用户ID
     * @param prize 奖品
     * @param topic 主题
     * @return prize
     */
    private PrizeDTO buildPrizeDTO(Long userId, Prize prize,String topic){
        PrizeDTO dto = new PrizeDTO();
        BeanUtils.copyProperties(prize,dto);
        Record record = buildRecord(prize, userId, topic);
        if(config.getAsynSwitch()){
          //  rabbitTemplate.convertAndSend("topicExchange", "topic.record", record);
        }else{
            recordDao.save(record);
        }
        return dto;
    }


    private Record buildRecord(Prize prize, Long userId, String topic){
        Record rd = new Record();
        BeanUtils.copyProperties(prize,rd);
        rd.setRecordId(IDUtil.id());
        rd.setUserId(userId);
        rd.setLotteryTopic(topic);
        return rd;
    }



    private Lottery getLottery(Long lotteryId){
        String activity = config.getActivity(lotteryId);
        if(StringUtils.isEmpty(activity)){
            throw new BizException(ErrorCode.LOTTERY.LOTTERY_STATUS_ERROR);
        }
        Lottery lottery = JSON.parseObject(activity, Lottery.class);
        long now = SystemTimer.currentTime();
        if(lottery.getState() != 1
                || lottery.getStartTime() > now
                || lottery.getEndTime() < now ){
            throw new BizException(ErrorCode.LOTTERY.LOTTERY_STATUS_ERROR);
        }
        return lottery;
    }


}
